#include "engine.h"
#include "internal_types.h"
#include <vector>
#include <string>
#include <GL/glew.h>
#include <GL/glfw.h>

#if _DEBUG
#include <iostream>
using namespace std;
#endif

namespace HW
{

////Internal functions definitions
void _i_InitGraphics(void);
void _i_registerCallbacks(void);
void _i_queryVidModes ( void );
void _i_querySpec(void);
void GLFWCALL _i_onResize(int width, int height);
int GLFWCALL _i_onClose( void );
void GLFWCALL _i_onRefresh( void );
////Internal functions definitions


int Graphics(int width, int height, int depth, int hertz, int flags)
{
	extern bool gWindowCreated;
	if ( gWindowCreated )
		glfwSetWindowSize ( width , height );
	else
	{
		AppLog ( "Initializing Renderer..." );

		bool lBackDepth = (flags & GRAPHICS_DEPTHBUFFER)!=0;
		bool lBackStencil = (flags & GRAPHICS_STENCILBUFFER)!=0;
		bool lBackAlpha = (flags & GRAPHICS_ALPHABUFFER)!=0;
		bool lFullScreen = depth > 0;
		int lBackColorBPP = (lBackAlpha)?(depth/4):(depth/3);

		glfwOpenWindowHint ( GLFW_REFRESH_RATE , hertz );
		glfwOpenWindowHint ( GLFW_WINDOW_NO_RESIZE , GL_TRUE ); //To be compatabile to Leadwerks.

		int res = glfwOpenWindow ( width , height , lBackColorBPP , lBackColorBPP , lBackColorBPP , (lBackAlpha)?(depth/4):0 , (lBackDepth)?24:0 , (lBackStencil)?8:0 , (lFullScreen)?GLFW_FULLSCREEN:GLFW_WINDOW );
		if ( res == GL_FALSE )
		{
			gWindowCreated = false;
			return 0;
		}

		GLenum err = glewInit();
		if (GLEW_OK != err)
		{
		  /* Problem: glewInit failed, something is seriously wrong. */
			gWindowCreated = false;
			return GL_FALSE;
		}

		if ( lFullScreen )
			ShowMouse ( );

		extern char gAppTitle[100];
		glfwSetWindowTitle ( gAppTitle );
		
		_i_querySpec();

		_i_registerCallbacks();
		
	}

	//TODO: get actual resulted window params here form GLFW

	gWindowCreated = true;
	return 1;
}

void Flip(int sync)
{
	extern int gVSync;

	if ( sync != gVSync )
	{
		gVSync = sync;
		glfwSwapInterval ( gVSync );
	}

	glfwSwapBuffers ( );

	extern float gLastFrameDur;
	static float gLastSwapTime;

	gLastFrameDur = (float(glfwGetTime())*1000.0f) - gLastSwapTime;
	gLastSwapTime = (float(glfwGetTime())*1000.0f);
}

int GraphicsWidth(void)
{
	extern int gBackWidth;
	return gBackWidth;
}

int GraphicsHeight(void)
{
	extern int gBackHeight;
	return gBackHeight;
}

int GraphicsModeExists(int width, int height, int depth, int hertz)
{
	if ( depth == 0 )
	{
		//TODO: just check for the boundaries (current desktop mode) here.
		return GL_TRUE;
	}

	extern std::vector<GLFWvidmode> gVidMode;

	for ( unsigned int i = 0 ; i < gVidMode.size() ; ++i )
	{
		if ( ( gVidMode[i].Width == width ) &&
			( gVidMode[i].Height == height ) &&
			( (gVidMode[i].RedBits+gVidMode[i].GreenBits+gVidMode[i].BlueBits) == (3*depth/4) ) )
			return GL_TRUE;
	}
	return GL_FALSE;
}

int GetGraphicsVendor(void)
{
	extern int gGPUVendor;
	return gGPUVendor;
}

int GetShaderModel(void)
{
	return 0;
}


////Internal functions implementations
void _i_InitGraphics(void)
{
	_i_queryVidModes ( );
}

void _i_registerCallbacks(void)
{
	glfwSetWindowSizeCallback ( _i_onResize );
	glfwSetWindowCloseCallback ( _i_onClose );
	glfwSetWindowRefreshCallback ( _i_onRefresh );

	extern void GLFWCALL _i_OnKeyChange( int key, int action );
	extern void GLFWCALL _i_OnChar( int character, int action );
	extern void GLFWCALL _i_OnMouseButton( int button, int action );
	extern void GLFWCALL _i_OnMouseMove( int x, int y );
	extern void GLFWCALL _i_OnMouseWheel( int pos );
	glfwSetKeyCallback ( _i_OnKeyChange );
	glfwSetCharCallback ( _i_OnChar );
	glfwSetMouseButtonCallback ( _i_OnMouseButton );
	glfwSetMousePosCallback ( _i_OnMouseMove );
	glfwSetMouseWheelCallback ( _i_OnMouseWheel );
}

void _i_queryVidModes ( void )
{
	int lMaxVidModes = 100;
	extern std::vector<GLFWvidmode> gVidMode;
	for ( int i = 0 ; i < lMaxVidModes ; ++i )
		gVidMode.push_back ( GLFWvidmode() );
	glfwGetVideoModes ( &gVidMode[0] , lMaxVidModes );
	for ( unsigned int i = 0 ; i < gVidMode.size() ; ++i )
	{
		if ( gVidMode[i].Width + gVidMode[i].Height == 0 )
		{
			gVidMode.resize ( i );
			break;
		}
	}

	#if _DEBUG
	for ( unsigned int i = 0 ; i < gVidMode.size() ; ++i )
		cout << gVidMode[i].Width << "	*	" << gVidMode[i].Height << "	" << gVidMode[i].RedBits << "," << gVidMode[i].GreenBits << "," << gVidMode[i].BlueBits << endl;
	#endif
}

void _i_querySpec ( void )
{
	std::string lLogStr;

	lLogStr = "OpenGL Version: ";
	lLogStr.append ( (char*)glGetString ( GL_VERSION ) );
	AppLog ( lLogStr.c_str() );
	lLogStr = "GLSL Version: ";
	lLogStr.append ( (char*)glGetString ( GL_SHADING_LANGUAGE_VERSION ) );
	AppLog ( lLogStr.c_str() );
	lLogStr = "Render device: ";
	lLogStr.append ( (char*)glGetString ( GL_RENDERER ) );
	AppLog ( lLogStr.c_str() );
	
	extern int gGPUVendor;
	const GLubyte* lstrGPUVendor = glGetString ( GL_VENDOR );
	if ( !strcmp((char*)lstrGPUVendor,"NVIDIA Corporation") )
		gGPUVendor = VENDOR_NVIDIA;
	else if ( !strcmp((char*)lstrGPUVendor,"ATI Technologies Inc.") )
		gGPUVendor = VENDOR_ATI;
	else
		gGPUVendor = VENDOR_UNKNOWN;
	lLogStr = "Vendor: ";
	lLogStr.append ( (char*)lstrGPUVendor );
	AppLog ( lLogStr.c_str() );

	char lQueryC[10];
	GLint lQueryI;

	lQueryI = glfwExtensionSupported ( "GL_ARB_draw_buffers" );
	_itoa_s ( lQueryI , lQueryC , 10 );
	lLogStr = "DrawBuffers2 supported: ";
	lLogStr.append ( lQueryC );
	AppLog ( lLogStr.c_str() );

	glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &lQueryI);
	_itoa_s ( lQueryI , lQueryC , 10 );
	lLogStr = lQueryC;
	lLogStr.append ( " texture units supported." );
	AppLog ( lLogStr.c_str() );

	lQueryI = glfwExtensionSupported ( "GL_ARB_draw_instanced" );
	_itoa_s ( lQueryI , lQueryC , 10 );
	lLogStr = "GPU instancing supported: ";
	lLogStr.append ( lQueryC );
	AppLog ( lLogStr.c_str() );

	glGetIntegerv(GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT, &lQueryI);
	_itoa_s ( lQueryI , lQueryC , 10 );
	lLogStr = "Max batch size: ";
	lLogStr.append ( lQueryC );
	AppLog ( lLogStr.c_str() );

	int lGLVerMaj, lGLVerMin, lGLVerRev;
	glfwGetGLVersion ( &lGLVerMaj , &lGLVerMin , &lGLVerRev );
	lQueryI = GL_FALSE;
	if ( ( lGLVerMaj > 3 ) ||
		( (lGLVerMaj==3) && (lGLVerMin==3) ) )
		lQueryI = GL_TRUE;
	_itoa_s ( lQueryI , lQueryC , 10 );
	lLogStr = "Shader model 4.0 supported: ";
	lLogStr.append ( lQueryC );
	AppLog ( lLogStr.c_str() );

	lQueryI = glfwExtensionSupported ( "GL_NV_conditional_render" );
	_itoa_s ( lQueryI , lQueryC , 10 );
	lLogStr = "Conditional render supported: ";
	lLogStr.append ( lQueryC );
	AppLog ( lLogStr.c_str() );
}

void GLFWCALL _i_onResize(int width, int height)
{
	extern int gBackWidth;
	extern int gBackHeight;

	gBackWidth = width;
	gBackHeight = height;

	glViewport ( 0 , 0 , width , height );

	extern TBuffer lBackBuffer;
	((Resource::_i_Buffer*)(lBackBuffer))->width = GraphicsWidth();
	((Resource::_i_Buffer*)(lBackBuffer))->height = GraphicsHeight();
}

int GLFWCALL _i_onClose( void )
{
	extern int gAppTerminated;
	gAppTerminated = GL_TRUE;
	return GL_FALSE;
}

void GLFWCALL _i_onRefresh( void )
{
}
////Internal functions implementations
}